﻿/*

The MIT License

Copyright (c) 2010 Cartesian Analytics, Inc. 

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

*/

using System;

namespace Pipra.Math
{

    /// <summary>
    /// A range which encompasses all values between two values, inclusive.
    /// </summary>
    /// <remarks>
    /// A range where the high and low values are equal is a range that covers only one value.
    /// </remarks>
    public struct Range :
        IRange<double>,
        IIntersectable<Range>,
        IContainable<Range>,
        ITouches<Range>,
        IOverlappable<Range>,
        ICanBeDisjoint<Range>,
        ICanContain<Range>,
        ICanCross<Range>,
        IHasDistance<double, Range>,
        IEquatable<Range>
    {

        /// <summary>
        /// Determines if two ranges are equal.
        /// </summary>
        /// <param name="a">A range.</param>
        /// <param name="b">A range.</param>
        /// <returns>True when the ranges are equal.</returns>
        public static bool operator ==(Range a, Range b)
        {
            return a.Equals(b);
        }

        /// <summary>
        /// Determines if two ranges are not equal.
        /// </summary>
        /// <param name="a">A range.</param>
        /// <param name="b">A range.</param>
        /// <returns>True when the ranges are not equal.</returns>
        public static bool operator !=(Range a, Range b)
        {
            return a.Equals(b);
        }
        /// <summary>
        /// The lowest value of the range.
        /// </summary>
        public readonly double Low;
        /// <summary>
        /// The highest value of the range.
        /// </summary>
        public readonly double High;

        /// <summary>
        /// Constructs a new range encompassing only the given value <paramref name="v"/>.
        /// </summary>
        /// <param name="v">A value.</param>
        public Range(double v)
        {
            Low = High = v;
        }

        /// <summary>
        /// Constructs a new range from <paramref name="a"/> to <paramref name="b"/>.
        /// </summary>
        /// <param name="a">A value.</param>
        /// <param name="b">A value.</param>
        public Range(double a, double b)
        {
            if (b < a)
            {
                Low = b;
                High = a;
            }
            else
            {
                Low = a;
                High = b;
            }
        }
        /// <summary>
        /// Constructs a range from <paramref name="r"/> with the same Low and High bounds.
        /// </summary>
        /// <param name="r">The range to copy from.</param>
        public Range(IRange<double> r)
            : this(r.Low,r.High)
        { }

        private Range(Range r, double v)
        {
            if(v < r.Low)
            {
                Low = v;
                High = r.High;
            }else if(v > r.High)
            {
                Low = r.Low;
                High = v;
            }else
            {
                Low = r.Low;
                High = r.High;
            }
        }

        private Range(Range a, Range b)
        {
            Low = System.Math.Min(a.Low, b.Low);
            High = System.Math.Min(a.High, b.High);
        }

        /// <summary>
        /// The lowest value of the range.
        /// </summary>
        double IRange<double>.Low
        {
            get { return Low; }
        }

        /// <summary>
        /// The highest value of the range.
        /// </summary>
        double IRange<double>.High
        {
            get { return High; }
        }

        /// <summary>
        /// The average of the lowest and highest value.
        /// </summary>
        public double Mid
        {
            get { return (Low + High) / 2.0; }
        }

        /// <summary>
        /// Calculates the magnitude of this range.
        /// </summary>
        /// <returns>The magnitude.</returns>
        public double GetMagnitude()
        {
            return High - Low;
        }

        /// <summary>
        /// Calculates the squared magnitude of this range.
        /// </summary>
        /// <returns>The squared magnitude.</returns>
        public double GetMagnitudeSquared()
        {
            double delta = High - Low;
            return delta * delta;
        }

        /// <summary>
        /// Calculates the distance between this range and <paramref name="value"/>
        /// </summary>
        /// <param name="value">The value to calculate distance to.</param>
        /// <returns>The resulting distance.</returns>
        public double Distance(double value)
        {
            return (
                (value < Low)
                ? (Low - value)
                : (
                    (High < value)
                    ? (value - High)
                    : 0
                )
            );
        }

        /// <summary>
        /// Calculates the squared distance between this range and <paramref name="value"/>
        /// </summary>
        /// <param name="value">The object to calculate squared distance to.</param>
        /// <returns>The resulting squared distance.</returns>
        public double DistanceSquared(double value)
        {
            double d = (
                (value < Low)
                ? (Low - value)
                : (
                    (High < value)
                    ? (value - High)
                    : 0
                )
            );
            return d * d;
        }

        /// <summary>
        /// Calculates the distance between this range and <paramref name="range"/>
        /// </summary>
        /// <param name="range">The object to calculate distance to.</param>
        /// <returns>The resulting distance.</returns>
        public double Distance(IRange<double> range)
        {
            return (
                (range.High < Low)
                ? (Low - range.High)
                : (
                    (High < range.Low)
                    ? (range.Low - High)
                    : 0
                )
            );
        }

        /// <summary>
        /// Calculates the squared distance between this range and <paramref name="range"/>
        /// </summary>
        /// <param name="range">The object to calculate squared distance to.</param>
        /// <returns>The resulting squared distance.</returns>
        public double DistanceSquared(IRange<double> range)
        {
            double d = Distance(range);
            return d * d;
        }

        /// <summary>
        /// Calculates the distance between this range and <paramref name="range"/>
        /// </summary>
        /// <param name="range">The object to calculate distance to.</param>
        /// <returns>The resulting distance.</returns>
        public double Distance(Range range)
        {
            return (
                (range.High < Low)
                ? (Low - range.High)
                : (
                    (High < range.Low)
                    ? (range.Low - High)
                    : 0
                )
            );
        }

        /// <summary>
        /// Calculates the squared distance between this range and <paramref name="range"/>
        /// </summary>
        /// <param name="range">The object to calculate squared distance to.</param>
        /// <returns>The resulting squared distance.</returns>
        public double DistanceSquared(Range range)
        {
            double d = Distance(range);
            return d * d;
        }

        /// <summary>
        /// Determines if this range is equal to some <paramref name="other"/> range.
        /// </summary>
        /// <param name="other">A range to compare with.</param>
        /// <returns>
        /// True if the current range is equal to the <paramref name="other"/> range; otherwise, false.
        /// </returns>
        public bool Equals(Range other)
        {
            return Low == other.Low
                && High == other.High
            ;
        }

        /// <summary>
        /// Determines if this range is equal to some <paramref name="other"/> range.
        /// </summary>
        /// <param name="other">A range to compare with.</param>
        /// <returns>
        /// True if the current range is equal to the <paramref name="other"/> range; otherwise, false.
        /// </returns>
        public bool Equals(IRange<double> other)
        {
            return null != other
                && Low == other.Low
                && High == other.High
            ;
        }

        /// <summary>
        /// Indicates whether the current range is equal to another value.
        /// </summary>
        /// <returns>
        /// true if the current range is equal to the <paramref name="other"/> value; otherwise, false.
        /// </returns>
        /// <param name="other">A value.</param>
        public bool Equals(double other)
        {
            return Low == other && High == other;
        }

        /// <summary>
        /// Indicates whether this range and a specified object are equal.
        /// </summary>
        /// <returns>
        /// true if <paramref name="obj"/> and this range are the same type and represent the same value; otherwise, false.
        /// </returns>
        /// <param name="obj">Another object to compare to.</param>
        /// <filterpriority>2</filterpriority>
        public override bool Equals(object obj)
        {
            return null != obj
                && (
                    (obj is Range) ? Equals((Range)obj)
                    :
                    (obj is IRange<double>) ? Equals(obj as IRange<double>)
                    :
                    (obj is double && Equals((double) obj))
                )
            ;
        }

        /// <summary>
        /// Returns the hash code for this instance.
        /// </summary>
        /// <returns>
        /// A 32-bit signed integer that is the hash code for this instance.
        /// </returns>
        /// <filterpriority>2</filterpriority>
        public override int GetHashCode()
        {
            return Low.GetHashCode();
        }

        /// <summary>
        /// Returns a <see cref="System.String"/> that represents this instance.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"/> that represents this instance.
        /// </returns>
        public override string ToString()
        {
            return String.Concat(Low, ',', High);
        }

        /// <summary>
        /// Determines if another <paramref name="value"/> intersects this range.
        /// </summary>
        /// <param name="value">A value to test intersection with.</param>
        /// <returns>True when the given <paramref name="value"/> intersects this range.</returns>
        public bool Intersects(double value)
        {
            return (Low <= value) && (value <= High);
        }

        /// <summary>
        /// Determines if another <paramref name="range"/> intersects this range.
        /// </summary>
        /// <param name="range">A range to test intersection with.</param>
        /// <returns>True when the given <paramref name="range"/> intersects this range.</returns>
        public bool Intersects(Range range)
        {
            return (Low <= range.High) && (range.Low <= High);
        }

        /// <summary>
        /// Determines if another <paramref name="range"/> intersects this range.
        /// </summary>
        /// <param name="range">A range to test intersection with.</param>
        /// <returns>True when the given <paramref name="range"/> intersects this range.</returns>
        public bool Intersects(IRange<double> range)
        {
            return (Low <= range.High) && (range.Low <= High);
        }
        /// <summary>
        /// Determines if the given value contains this range and this range does not intersect the other exterior.
        /// </summary>
        /// <param name="value">The value to test.</param>
        /// <returns>True if this range is within the given value.</returns>
        public bool Within(double value)
        {
            return Low == value && High == value;
        }
        /// <summary>
        /// Determines if the given range contains this range and this range does not intersect the other exterior.
        /// </summary>
        /// <param name="range">The range to test.</param>
        /// <returns>True if this range is within the given range.</returns>
        public bool Within(Range range)
        {
            return range.Low <= Low && range.High >= High;
        }
        /// <summary>
        /// Determines if the given range contains this range and this range does not intersect the other exterior.
        /// </summary>
        /// <param name="range">The range to test.</param>
        /// <returns>True if this range is within the given range.</returns>
        public bool Within(IRange<double> range)
        {
            return range.Low <= Low && range.High >= High;
        }

        /// <summary>
        /// Determines if this range and some <paramref name="value"/> share only boundaries.
        /// </summary>
        /// <param name="value">A value.</param>
        /// <returns>True when only boundaries occupy the same space.</returns>
        public bool Touches(double value)
        {
            return (Low == value || High == value) && Low != High;
        }
        /// <summary>
        /// Determines if this range and some <paramref name="other"/> share only boundaries.
        /// </summary>
        /// <param name="other">A range.</param>
        /// <returns>True when only boundaries occupy the same space.</returns>
        public bool Touches(Range other)
        {
            return Low == other.High || High == other.Low;
        }
        /// <summary>
        /// Determines if this range and some <paramref name="other"/> share only boundaries.
        /// </summary>
        /// <param name="other">A range.</param>
        /// <returns>True when only boundaries occupy the same space.</returns>
        public bool Touches(IRange<double> other)
        {
            return Low == other.High || High == other.Low;
        }

        /// <summary>
        /// Determines if this range and some <paramref name="other"/> value share some space of the same dimension.
        /// </summary>
        /// <param name="other">A range.</param>
        /// <returns>True when this range and a value share space of the same dimension.</returns>
        public bool Overlaps(double other)
        {
            return Low == other && High == other;
        }
        /// <summary>
        /// Determines if this range and some <paramref name="other"/> share some space of the same dimension.
        /// </summary>
        /// <param name="other">A range.</param>
        /// <returns>True when the ranges share space of the same dimension.</returns>
        public bool Overlaps(Range other)
        {
            return (Low < other.High) && (other.Low < High);
        }
        /// <summary>
        /// Determines if this range and some <paramref name="other"/> share some space of the same dimension.
        /// </summary>
        /// <param name="other">A range.</param>
        /// <returns>True when the ranges share space of the same dimension.</returns>
        public bool Overlaps(IRange<double> other)
        {
            return (Low < other.High) && (other.Low < High);
        }

        /// <summary>
        /// Determines if this range and some <paramref name="other"/> value share no space.
        /// </summary>
        /// <param name="other">A value.</param>
        /// <returns>True if this range and a value do not occupy any of the same space.</returns>
        public bool Disjoint(double other)
        {
            return other < Low || other > High;
        }
        /// <summary>
        /// Determines if this range and some <paramref name="other"/> share no space.
        /// </summary>
        /// <param name="other">A range.</param>
        /// <returns>True if the ranges do not occupy any of the same space.</returns>
        public bool Disjoint(Range other)
        {
            return other.High < Low || other.Low > High;
        }
        /// <summary>
        /// Determines if this range and some <paramref name="other"/> share no space.
        /// </summary>
        /// <param name="other">A range.</param>
        /// <returns>True if the ranges do not occupy any of the same space.</returns>
        public bool Disjoint(IRange<double> other)
        {
            return other.High < Low || other.Low > High;
        }

        /// <summary>
        /// Determines if some <paramref name="other"/> value is within the interior of this range.
        /// </summary>
        /// <param name="other">A value.</param>
        /// <returns>True when this range contains another value.</returns>
        public bool Contains(double other)
        {
            return Low <= other && other <= High;
        }
        /// <summary>
        /// Determines if some <paramref name="other"/> range is within the interior of this range.
        /// </summary>
        /// <param name="other">A range.</param>
        /// <returns>True when this range contains another.</returns>
        public bool Contains(Range other)
        {
            return Low <= other.Low && other.High <= High;
        }
        /// <summary>
        /// Determines if some <paramref name="other"/> range is within the interior of this range.
        /// </summary>
        /// <param name="other">A range.</param>
        /// <returns>True when this range contains another.</returns>
        public bool Contains(IRange<double> other)
        {
            return Low <= other.Low && other.High <= High;
        }

        /// <summary>
        /// Determines if this range crosses some <paramref name="other"/> value.
        /// </summary>
        /// <param name="other">The value to test.</param>
        /// <returns>True if this range crosses the <paramref name="other"/> value.</returns>
        public bool Crosses(double other)
        {
            return other > Low && other < High;
        }
        /// <summary>
        /// Determines if this range crosses some <paramref name="other"/> range.
        /// </summary>
        /// <param name="other">The range to test.</param>
        /// <returns>True if this range crosses the <paramref name="other"/>.</returns>
        public bool Crosses(Range other)
        {
            return (
                Low == High
                ? other.Crosses(Low)
                : other.Low == other.High &&Crosses(other.Low)
            );

        }
        /// <summary>
        /// Determines if this range crosses some <paramref name="other"/> range.
        /// </summary>
        /// <param name="other">The range to test.</param>
        /// <returns>True if this range crosses the <paramref name="other"/>.</returns>
        public bool Crosses(IRange<double> other)
        {
            return Crosses(new Range(other));
        }

        /// <summary>
        /// Creates a new range encompassing this range and the given <paramref name="value"/>.
        /// </summary>
        /// <param name="value">A value.</param>
        /// <returns>A range.</returns>
        public Range Union(double value)
        {
            return new Range(this,value);
        }
        IRange<double> IRange<double>.Union(double value)
        {
            return Union(value);
        }
        /// <summary>
        /// Creates a new range encompassing this range and the given <paramref name="range"/>.
        /// </summary>
        /// <param name="range">A range.</param>
        /// <returns>A range.</returns>
        public Range Union(Range range)
        {
            return new Range(this,range);
        }
        /// <summary>
        /// Creates a new range encompassing this range and the given <paramref name="range"/>.
        /// </summary>
        /// <param name="range">A range.</param>
        /// <returns>A range.</returns>
        public Range Union(IRange<double> range)
        {
            return Union(new Range(range));
        }
        IRange<double> IRange<double>.Union(IRange<double> range)
        {
            return Union(range);
        }

        /// <summary>
        /// Gets a copy of the range at the same center point but a scaled size.
        /// </summary>
        /// <param name="scale">The scaling factor.</param>
        /// <returns>A range.</returns>
        public Range GetScaled(double scale)
        {
            return GetResized((High - Low) * scale);
        }
        IRange<double> IRange<double>.GetScaled(double scale)
        {
            return GetScaled(scale);
        }

        /// <summary>
        /// Gets a copy of the range at the same center point but with the given size.
        /// </summary>
        /// <param name="size">The size.</param>
        /// <returns>A range.</returns>
        public Range GetResized(double size)
        {
            return (
                size < 0
                    ? GetResized(-size)
                    : (
                            0 < size
                                ? new Range(((Low + High) + size)/2.0, High - size)
                                : new Range(Mid)
                        )
            );
        }
        IRange<double> IRange<double>.GetResized(double size)
        {
            return GetResized(size);
        }

        /// <summary>
        /// Gets a copy of the range at the given <paramref name="center"/> point but with the same size.
        /// </summary>
        /// <param name="center">The center location.</param>
        /// <returns>A range.</returns>
        public Range GetCentered(double center)
        {
            double oldLen = High - Low;
            double high = center + (oldLen/2.0);
            return new Range(high,high - oldLen);
        }
        IRange<double> IRange<double>.GetCentered(double center)
        {
            return GetCentered(center);
        }

    }
}
